package com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.config.configuration;

import com.zhangfb95.crosschain.infra.exception.BusinessException;
import com.zhangfb95.crosschain.infra.model.Result;
import com.zhangfb95.crosschain.infra.util.tls.client.ReactorNettyWebClientBuilder;
import com.zhangfb95.crosschain.infra.util.tls.client.WebClientSslProperties;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.config.properties.CrossChainServerProperties;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.config.properties.FabricProperties;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.data.api.crosschainserver.PluginApi;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.extend.fabricsupport.StdCryptoSuite;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.dto.ChainPathInfo;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.enums.AccessTypeEnum;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.util.CertUtil;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.ChainPathService;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.FabricService;
import com.zhangfb95.crosschain.protocol.exc.request.SendExcReq;
import com.zhangfb95.crosschain.protocol.exc.response.SendExcRes;
import com.zhangfb95.crosschain.protocol.routerplugincontroller.request.GetAccountPluginReq;
import com.zhangfb95.crosschain.protocol.routerplugincontroller.request.SignPluginReq;
import com.zhangfb95.crosschain.protocol.routerplugincontroller.response.GetAccountPluginRes;
import com.zhangfb95.crosschain.protocol.routerplugincontroller.response.SignPluginRes;
import org.hyperledger.fabric.sdk.BlockEvent;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import java.security.PrivateKey;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zhangfubing
 * @since 2023/11/4
 */
@Configuration
public class CrossChainServerConfiguration {

    @Autowired
    private FabricProperties fabricProperties;
    @Autowired
    private CrossChainServerProperties crossChainServerProperties;
    @Autowired
    private StdCryptoSuite stdCryptoSuite;
    @Autowired
    private ChainPathService chainPathService;
    @Autowired
    private BeanFactory beanFactory;

    @Bean
    public PluginApi pluginApi() {
        if (AccessTypeEnum.LOCAL.is(crossChainServerProperties.getAccessType())) {
            return buildLocalPluginApi();
        } else {
            return buildRemotePluginApi();
        }
    }

    private PluginApi buildLocalPluginApi() {
        Map<String, CrossChainServerProperties.LocalInfo> localInfoMap =
                crossChainServerProperties.getLocalInfos().stream()
                        .collect(Collectors.toMap(CrossChainServerProperties.LocalInfo::getChainPath, o -> o));

        return new PluginApi() {
            @Override
            public Result<GetAccountPluginRes> getAccount(GetAccountPluginReq req) {
                CrossChainServerProperties.LocalInfo localInfo = localInfoMap.get(req.getChainPath());
                if (localInfo == null) {
                    return Result.fail("localInfo not find");
                }

                GetAccountPluginRes res = new GetAccountPluginRes()
                        .setPublicKey(localInfo.getPublicKey())
                        .setPropertyMap(localInfo.getPropertyMap());
                return Result.ok(res);
            }

            @Override
            public Result<SignPluginRes> sign(SignPluginReq req) {
                CrossChainServerProperties.LocalInfo localInfo = localInfoMap.get(req.getChainPath());
                if (localInfo == null) {
                    return Result.fail("localInfo not find");
                }

                try {
                    PrivateKey privateKey = CertUtil.getPrivateKey(localInfo.getPrivateKey());
                    byte[] signedBytes = stdCryptoSuite.sign(privateKey, req.getBytes());
                    return Result.ok(new SignPluginRes().setSignedBytes(signedBytes));
                } catch (CryptoException e) {
                    return Result.fail(e.getMessage());
                }
            }

            @Override
            public Result<SendExcRes> send(SendExcReq req) {
                if (req.getChainPath() == null) {
                    throw new BusinessException("the local accessType, chainPath is null");
                }

                if (!req.getChainPath().startsWith(fabricProperties.getChainName() + ".")) {
                    throw new BusinessException("the local accessType only supports its own chain.");
                }

                ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
                String channelName = chainPathInfo.getChannelName();
                String chaincodeName = chainPathInfo.getChaincodeName();
                FabricService fabricService = beanFactory.getBean(FabricService.class);
                BlockEvent.TransactionEvent transactionEvent = fabricService.sendTransaction(
                        req.getServerId(),
                        req.getUserId(),
                        req.getChainPath(),
                        channelName,
                        chaincodeName,
                        req.getMethod(),
                        req.getArgs());
                SendExcRes res = new SendExcRes()
                        .setTransactionID(transactionEvent.getTransactionID())
                        .setResult(new String(transactionEvent.getTransactionActionInfo(0).getProposalResponsePayload()));
                return Result.ok(res);
            }
        };
    }

    private PluginApi buildRemotePluginApi() {
        CrossChainServerProperties.RemoteInfo remoteInfo = crossChainServerProperties.getRemoteInfo();
        WebClientSslProperties sslProperties = new WebClientSslProperties()
                .setServerUrl(remoteInfo.getUrl())
                .setSslType(remoteInfo.getSslType())
                .setCaCert(remoteInfo.getCaCert())
                .setSslKey(remoteInfo.getSslKey())
                .setSslCert(remoteInfo.getSslCert());
        WebClient webClient = ReactorNettyWebClientBuilder.build(sslProperties);
        HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
                .builder(WebClientAdapter.forClient(webClient))
                .build();
        return httpServiceProxyFactory.createClient(PluginApi.class);
    }
}
